查看原文
其他

Java 9 新特性 —— module 模块系统

2017-09-28 polly 开源中国


官方文档:https://docs.oracle.com/javase/9/index.html

关于 java9的新特性,官方原文:https://docs.oracle.com/javase/9/whatsnew/toc.htm

这玩意就是一个列表,具体的技术细节需要根据官方文档挖一挖。


modular-模块系统


java9的模块化,从一个独立的开源项目而来,名为Jigsaw。

项目官网:http://openjdk.java.net/projects/jigsaw/


为什么要使用模块化


java开发者都知道,使用java开发应用程序都会遇到一个问题,Jar hell,他就像windows里的dll hell。

比如我们启动一个不算大的应用,但依赖了很多的jar,如下图:

摘自:Mark Reinhold的演讲 https://www.youtube.com/watch?v=l1s7R85GF1A

这是很常见的。虽然你可以使用 "java -Djava.ext.dirs=lib xxx" 让命令行小一些,但不可否认,他的classpath就是那么长。顺便说一句,java9中不允许使用extdirs了。

另一方面,jdk本身有很多的api:

对于一些小设备,它太庞大了。

helloworld

还是习惯先来一个helloworld。在此之前,需要先检查一下你的java版本:

如果不是java9,而是 1.8、1.7,那么慢走不送。

创建主类

首先创建一个java类,就叫Demo吧。

文件保存为:src/com/pollyduan/modular/Demo.java

编译:

打包jar并执行

--class-path 开关可以简写:

当然我们可以为jar指定主类,来简化运行:

当然我们可以为jar指定主类,来简化运行:

需要在MANIFEST.MF 中增加上面一行,即可直接运行:

创建模块

src/module-info.java


我们写了一个空的模块,命名为hello。

编译模块


反编译看一下:

为什么我们写了一个空的模块,反编译多了一行?先不用管,后面会说明为什么。

打包模块


运行模块

这里和传统的执行jar不一样了,这里不需要classpath,而是module-path。

同样命令行可以简写成:

模块可以增加Main-Class 吗?java9的jar提供了一个create开关,用这种方式打包,可以为module指定主类:

再次运行模块,命令行就会更简单了。


Jigsaw 的设计目标


让开发者构建和维护一个大型的库或应用程序更容易;

提高javaSE平台及JDK实现的安全性和可维护性;

提升应用的性能;

在javase及JDK平台,让应用更小以便于部署于更小的计算单元及紧密的云部署系统。


什么是 modules


为了解决这些问题,jdk在package上层,封装了一层。


那到底 module 是什么?

module是一个包的容器。module仅仅需要导出模块依赖的包。


创建一个module


声明一个module

cat module-info.java


和package-info.java 类似,它也用一个独立的java文件保存,名为 module-info.java。

创建需要导出的类

暂时,类的内容不重要,可以先写一个空类,这里只列出目录结构:


编译模块

打包模块

检查jar结构:

引用模块

现在我们已经有了模块 com.foo.bar-1.0.jar,那么在定义其他模块,就可以使用requires关键字引用这个模块了。



內建的 module


jdk原生的包被归并到內建的module里,如java.base模块:


所有的应用都会默认依赖 java.base,就像以前我们不用显式的 "import java.lang.*;" 一样。

这里验证了前面helloworld中,为什么反编译模块文件之后会多了一个:"requires java.base;"。

下面的 com.foo.app 模块,不需要显式地引入java.base:

如果此时com.foo.bar 增加了 com.foo.baz 模块的引用。

那么,我们知道 com.foo.bar 也隐式 引入了 java.base。

同样的道理,com.foo.baz 模块也隐式引用了 java.base:


可靠的配置


继续深入下去,我们知道 java.sql 引用了其他大量的api,那么下图就不难理解了。

目前的模块结构,称为可读的模块,提供了可靠的配置。

如果引用了不存在的module,和jar一样,你同样会触发 xx not found.

编译时:

运行时:


可访问的类型


如果引用的模块没有导出某个类,那么是不可访问的,这称为强封装。

比如 com.foo.bar 模块中有一个内部类BetaImpl:

那么在 com.foo.bar 模块的主动引用模块 com.foo.app 中如下使用 BeatImpl:

在编译时,会触发异常:

就是说:BetaImpl不可访问,因为包 com.foo.bar.beta.internal 包没有被导出。

同样,即便使用导出版本编辑成功,而运行时引用了未导出版本模块:


查看內建的模块



查看更多内建模块:


helloworld 进阶


从helloworld的基础上,增加一个模块的依赖。

先来回顾一下helloworld的目录结构:


增加一个模块service,其中service目录和module目录同级。

创建服务类

service/src/com/pollyduan/service/HelloService.java

声明service模块

service/src/module-info.java

编译service模块

打包service模块

修改helloworld模块

module/src/module-info.java

修改helloworld主类使用service中的方法

module/src/com/pollyduan/modular/Demo.java

重新编译打包helloworld

打完收工。


模块相关的工具


原有的javac/javap等就不说了,这里只列举新增的几个。更多参考:https://docs.oracle.com/javase/9/tools/tools-and-command-reference.htm#JSWOR-GUID-55DE52DF-5774-4AAB-B334-E026FBAE6F34

jlink

模块整理工具,用于将一系列模块聚合、优化,打包到一个自定义的镜像中。这里说的是jre镜像,不是jar。

如果我们只引用了java.base 模块,那么可以打包时可以选择性地打包:

这时输出的jre就是一个完整可用的jre,他和原生jdk的大小相差很大:

这样,我们可以把自己的模块也打包进去。

注意,module-path的值采用classpath同样的分隔符,如windows里的分号和linux里的冒号;而add-modules 开关的值是使用逗号分隔的。

这样,我们打包了一个只有30M的jre,而且,把自己的module也打包进去了。然后呢?直接执行模块看看:


jlink还提供了一个launcher开关,可以将我们的模块编译成和java命令一样的可执行文件,放在 jre/bin 中。

请留意launcher的格式——"[命令]=[模块]",为了区分,命令使用了首字母大写。

jlink的开关很多,功能不仅于此,如下可以将已经很小的jre继续压缩:


jdeps

这是一个java类文件的依赖分析器。


jmod

用于创建jmod文件,以及查看已存在的jmod文件。

创建jmod文件:


jdeprscan

这是一个针对jar的静态的分析工具,查找其依赖的api。



模块小结


关键词


module和jar的区别

模块需要注意的问题

module 的依赖,同样存在循环依赖问题,需要注意。如:模块A,requires B; 模块B有 requires A。

IDE是否支持?传统的IDE都是基于classpath管理项目,现在需要支持基于module-path

module打包的jar,你仍然可以当做普通jar来用,没有人阻止你,至少目前是这样的。不过这并不是说module完全没有意义,就像class文件中的成员设置为私有,不允许外部访问,你完全可以通过反射去访问它,一个道理。

模块的应用场景

首先,最突出的用法,就是使用jlink打包自定义的镜像,分发到小计算单元中运行,如docker,嵌入式设备。

其次,将来必定会有越来越多的容器来支持直接运行模块。

然后,他对于应用的热插拔的插件场景中,会有一席之地。

最后,就是代替jar方式运行的模块运行方式。

拭目以待。



推荐阅读

可能是国内最火的开源项目 —— C/C++ 篇

从 UNIX 到 GitHub:十个关于自由和开源软件历史的重要事件

终于,期待已久的 Java 9 正式发布了!

这些优秀的主流代码编辑器,你用过多少款?

点击“阅读原文”查看更多精彩内容

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存